package com.zhl.auth.config;

import com.zhl.auth.service.ZhlClientDetailsService;
import com.zhl.auth.service.ZhlUserDetailsServiceImpl;
import com.zhl.common.constant.SecurityConstants;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * @author 凌晨
 * @Title: AuthorizationServerConfig
 * @Description TODO
 * @date： 2020/10/20 20:03
 * @version： V1.0
 */
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    // 数据库连接池对象
    private final DataSource dataSource;

    //从数据库中查询出客户端信息
    @Autowired
    private ClientDetailsService clientDetailsService;

    // token保存策略
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    // 授权模式专用对象
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    // 认证业务对象
    private final UserDetailsService userDetailsService;

    // 指定客户端登录信息来源
    @Override
    @SneakyThrows
    public void configure(ClientDetailsServiceConfigurer clients) {
        ZhlClientDetailsService clientDetailsService = new ZhlClientDetailsService(dataSource);
        clientDetailsService.setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
        clientDetailsService.setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);
        clients.withClientDetails(clientDetailsService);
    }

    //令牌管理服务
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service=new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);//客户端详情服务
        service.setSupportRefreshToken(true);//支持刷新令牌
        service.setTokenStore(tokenStore);//令牌存储策略
        //令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);

        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }

    // 授权码模式数据来源
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        return new JdbcAuthorizationCodeServices(dataSource);//设置授权码模式的授权码如何存取
    }

    //
    // 这个对象的实例可以完成令牌服务以及令牌endpoint配置
    // /oauth/authorize：授权端点。
    // /oauth/token：令牌端点。
    // /oauth/conﬁrm_access：用户确认授权提交端点。
    // /oauth/error：授权服务错误信息端点。
    // /oauth/check_token：用于资源服务访问的令牌解析端点。
    // /oauth/token_key：提供公有密匙的端点，如果你使用JWT令牌的话
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                // 认证管理器，当你选择了资源所有者密码（password）授权类型的时候，请设置 这个属性注入一个 AuthenticationManager 对象
                .authenticationManager(authenticationManager)//认证管理器
                .authorizationCodeServices(authorizationCodeServices)//授权码服务
                // ：如果你设置了这个属性的话，那说明你有一个自己的 UserDetailsService 接口的实现，
                // 或者你可以把这个东西设置到全局域上面去（例如 GlobalAuthenticationManagerConﬁgurer 这个配置对 象），
                // 当你设置了这个之后，那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查，
                // 用来确保这个账号是否仍然有效，假如说你禁用了这个账户的话
                .userDetailsService(userDetailsService)
                .tokenServices(tokenService())//令牌管理服务
                // ：这个属性是用来设置授权码服务的（即 AuthorizationCodeServices 的实例对 象），
                // 主要用于 "authorization_code" 授权码类型模式。
//                .authorizationCodeServices()
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    // 检查token的策略
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security){
        security
                .tokenKeyAccess("permitAll()")                    //oauth/token_key是公开
                .checkTokenAccess("permitAll()")                  //oauth/check_token公开
                .allowFormAuthenticationForClients()				//表单认证（申请令牌）
        ;
    }
}
